#include "JNode.h"

myjson::JNode::JNode()
{
	this->createNull();
}

myjson::JNode::JNode(const myjson::JNode& other) :node_type(other.node_type)
{
	switch (other.node_type)
	{
	case myjson::JNodeType::JSON_BOOL:
		this->node_value.json_bool = other.node_value.json_bool;
		break;
	case myjson::JNodeType::JSON_INT:
		this->node_value.json_int = other.node_value.json_int;
		break;
	case myjson::JNodeType::JSON_DOUBLE:
		this->node_value.json_double = other.node_value.json_double;
		break;
	case myjson::JNodeType::JSON_STRING:
		this->node_value.json_string = new std::string(*other.node_value.json_string);
		break;
	case myjson::JNodeType::JSON_ARRAY:
		this->node_value.json_array = new std::vector<myjson::JNode>(*other.node_value.json_array);
		break;
	case myjson::JNodeType::JSON_OBJECT:
		this->node_value.json_object = new std::unordered_map<std::string, myjson::JNode>(*other.node_value.json_object);
		break;
	default:
		break;
	}
}

myjson::JNode::JNode(myjson::JNode&& other) noexcept :
	node_type(other.node_type),
	node_value(other.node_value)
{
	other.node_value.json_double = 0.0;
}

myjson::JNode::JNode(const bool& value)
{
	this->createBool(value);
}

myjson::JNode::JNode(const std::int64_t& value)
{
	this->createInt(value);
}

myjson::JNode::JNode(const double& value)
{
	this->createDouble(value);
}

myjson::JNode::JNode(const std::string& value)
{
	this->createString(value);
}

myjson::JNode::JNode(const char* value)
{
	if (value == nullptr)
	{
		this->createNull();
	}
	else
	{
		this->createString(value);
	}
}

myjson::JNode::JNode(const std::vector<myjson::JNode>& value)
{
	this->createArray(value);
}

myjson::JNode::JNode(const std::unordered_map<std::string, myjson::JNode>& value)
{
	this->createObject(value);
}

myjson::JNode::JNode(myjson::JNodeType type)
{
	this->createJNodeWithType(type);
}

myjson::JNode::~JNode()
{
	this->cleanup();
}

void myjson::JNode::createNull()
{
	this->node_type = myjson::JNodeType::JSON_NULL;
}

void myjson::JNode::createBool(const bool& value)
{
	this->node_type = myjson::JNodeType::JSON_BOOL;
	this->node_value.json_bool = value;
}

void myjson::JNode::createInt(const std::int64_t& value)
{
	this->node_type = myjson::JNodeType::JSON_INT;
	this->node_value.json_int = value;
}

void myjson::JNode::createDouble(const double& value)
{
	this->node_type = myjson::JNodeType::JSON_DOUBLE;
	this->node_value.json_double = value;
}

void myjson::JNode::createString(const std::string& value)
{
	this->node_type = myjson::JNodeType::JSON_STRING;
	this->node_value.json_string = new std::string(value);
}

void myjson::JNode::createArray(const std::vector<myjson::JNode>& value)
{
	this->node_type = myjson::JNodeType::JSON_ARRAY;
	this->node_value.json_array = new std::vector<myjson::JNode>(value);
}

void myjson::JNode::createObject(const std::unordered_map<std::string, myjson::JNode>& value)
{
	this->node_type = myjson::JNodeType::JSON_OBJECT;
	this->node_value.json_object = new std::unordered_map<std::string, myjson::JNode>(value);
}

void myjson::JNode::createJNodeWithType(myjson::JNodeType type)
{
	this->node_type = type;
	switch (this->node_type)
	{
	case myjson::JNodeType::JSON_NULL:
		break;
	case myjson::JNodeType::JSON_BOOL:
		this->node_value.json_bool = false;
		break;
	case myjson::JNodeType::JSON_INT:
		this->node_value.json_int = 0;
		break;
	case myjson::JNodeType::JSON_DOUBLE:
		this->node_value.json_double = 0.0;
		break;
	case myjson::JNodeType::JSON_STRING:
		this->node_value.json_string = new std::string("");
		break;
	case myjson::JNodeType::JSON_ARRAY:
		this->node_value.json_array = new std::vector<myjson::JNode>();
		break;
	case myjson::JNodeType::JSON_OBJECT:
		this->node_value.json_object = new std::unordered_map<std::string, myjson::JNode>();
		break;
	default:
		break;
	}
}

void myjson::JNode::cleanup()
{
	switch (this->node_type)
	{
	case myjson::JNodeType::JSON_STRING:
		delete this->node_value.json_string;
		this->node_value.json_string = nullptr;
		break;
	case myjson::JNodeType::JSON_ARRAY:
		delete this->node_value.json_array;
		this->node_value.json_array = nullptr;
		break;
	case myjson::JNodeType::JSON_OBJECT:
		delete this->node_value.json_object;
		this->node_value.json_object = nullptr;
		break;
	default:
		break;
	}
	this->node_type = myjson::JNodeType::JSON_NULL;
	this->node_value.json_double = 0.0l;
}

void myjson::JNode::updateObjectKeyValue(const std::string& key, const myjson::JNode& value)
{
	if (this->node_type != myjson::JNodeType::JSON_OBJECT)
	{
		this->cleanup();
		this->node_type = myjson::JNodeType::JSON_OBJECT;
		this->node_value.json_object = new std::unordered_map<std::string, myjson::JNode>{};
	}
	(*this->node_value.json_object)[key] = value;
}

void myjson::JNode::updateObjectKeyValue(const std::string& key, myjson::JNode&& value)
{
	if (this->node_type != myjson::JNodeType::JSON_OBJECT)
	{
		this->cleanup();
		this->node_type = myjson::JNodeType::JSON_OBJECT;
		this->node_value.json_object = new std::unordered_map<std::string, myjson::JNode>{};
	}
	(*this->node_value.json_object)[key] = std::move(value);
}

void myjson::JNode::deleteObjectKey(const std::string& key)
{
	if (this->node_type != myjson::JNodeType::JSON_OBJECT)
	{
		throw std::logic_error("The current node's type is not \"object\"");
	}
	this->node_value.json_object->erase(key);
}

void myjson::JNode::addArrayValue(const myjson::JNode& value)
{
	if (this->node_type != myjson::JNodeType::JSON_ARRAY)
	{
		this->cleanup();
		this->node_type = myjson::JNodeType::JSON_ARRAY;
		this->node_value.json_array = new std::vector<myjson::JNode>{};
	}
	this->node_value.json_array->push_back(value);
}

void myjson::JNode::addArrayValue(myjson::JNode&& value)
{
	if (this->node_type != myjson::JNodeType::JSON_ARRAY)
	{
		this->cleanup();
		this->node_type = myjson::JNodeType::JSON_ARRAY;
		this->node_value.json_array = new std::vector<myjson::JNode>{};
	}
	this->node_value.json_array->push_back(std::move(value));
}

void myjson::JNode::popArrayValue()
{
	if (this->node_type != myjson::JNodeType::JSON_ARRAY)
	{
		throw std::logic_error("The current node's type is not \"array\"");
	}
	if (!this->node_value.json_array->empty())this->node_value.json_array->pop_back();
}

std::string myjson::JNode::toJSONText()
{
	std::string result = "";
	std::vector<myjson::JNode>* jnode_array = nullptr;
	std::unordered_map<std::string, myjson::JNode>* jnode_map = nullptr;
	switch (this->node_type)
	{
	case myjson::JNodeType::JSON_NULL:
		result += "null";
		break;
	case myjson::JNodeType::JSON_BOOL:
		if (this->node_value.json_bool)
		{
			result += "true";
		}
		else
		{
			result += "false";
		}
		break;
	case myjson::JNodeType::JSON_INT:
		result += std::to_string(this->node_value.json_int);
		break;
	case myjson::JNodeType::JSON_DOUBLE:
		result += std::to_string(this->node_value.json_double);
		break;
	case myjson::JNodeType::JSON_STRING:
		result += ("\"" + *(this->node_value.json_string) + "\"");
		break;
	case myjson::JNodeType::JSON_ARRAY:
		result += "[";
		jnode_array = this->node_value.json_array;
		if (jnode_array->size() > 0)
		{
			result += (*jnode_array)[0].toJSONText();
			for (std::size_t i = 1; i < jnode_array->size(); i++)
			{
				result += ",";
				result += (*jnode_array)[i].toJSONText();
			}
		}
		result += "]";
		break;
	case myjson::JNodeType::JSON_OBJECT:
		result += "{";
		jnode_map = this->node_value.json_object;
		if (jnode_map->size() > 0)
		{
			std::unordered_map<std::string, myjson::JNode>::iterator iter = jnode_map->begin();
			std::string cur_key = "\"" + iter->first + "\"";
			std::string cur_value = iter->second.toJSONText();
			result += cur_key + ":" + cur_value;
			iter++;
			for (; iter != jnode_map->end(); iter++)
			{
				result += ",";
				cur_key = "\"" + iter->first + "\"";
				cur_value = iter->second.toJSONText();
				result += cur_key + ":" + cur_value;
			}
		}
		result += "}";
		break;
	default:
		throw std::runtime_error("JSON ndoe error!");
		break;
	}
	return result;
}

std::string myjson::JNode::toFormatJSONText(const std::uint64_t& indent)
{
	std::string result = "";
	std::vector<myjson::JNode>* jnode_array = nullptr;
	std::unordered_map<std::string, myjson::JNode>* jnode_map = nullptr;
	std::string indent_str = "";
	for (std::uint64_t i = 0; i < indent; i++)
	{
		indent_str += '\t';
	}
	switch (this->node_type)
	{
	case myjson::JNodeType::JSON_NULL:
		result += "null";
		break;
	case myjson::JNodeType::JSON_BOOL:
		if (this->node_value.json_bool)
		{
			result += "true";
		}
		else
		{
			result += "false";
		}
		break;
	case myjson::JNodeType::JSON_INT:
		result += std::to_string(this->node_value.json_int);
		break;
	case myjson::JNodeType::JSON_DOUBLE:
		result += std::to_string(this->node_value.json_double);
		break;
	case myjson::JNodeType::JSON_STRING:
		result += ("\"" + *(this->node_value.json_string) + "\"");
		break;
	case myjson::JNodeType::JSON_ARRAY:
		result += "[\n";
		jnode_array = this->node_value.json_array;
		if (jnode_array->size() > 0)
		{
			result += indent_str;
			result += (*jnode_array)[0].toFormatJSONText(indent + 1);
			for (std::size_t i = 1; i < jnode_array->size(); i++)
			{
				result += ",\n";
				result += indent_str;
				result += (*jnode_array)[i].toFormatJSONText(indent + 1);
			}
		}
		if (indent_str != "")indent_str.pop_back();
		result += "\n" + indent_str + "]";
		break;
	case myjson::JNodeType::JSON_OBJECT:
		result += "{\n";
		jnode_map = this->node_value.json_object;
		if (jnode_map->size() > 0)
		{
			std::unordered_map<std::string, myjson::JNode>::iterator iter = jnode_map->begin();
			std::string cur_key = "\"" + iter->first + "\"";
			std::string cur_value = iter->second.toFormatJSONText(indent + 1);
			result += indent_str;
			result += cur_key + ": " + cur_value;
			iter++;
			for (; iter != jnode_map->end(); iter++)
			{
				result += ",\n";
				cur_key = "\"" + iter->first + "\"";
				cur_value = iter->second.toFormatJSONText(indent + 1);
				result += indent_str;
				result += cur_key + ": " + cur_value;
			}
		}
		if (indent_str != "")indent_str.pop_back();
		result += "\n" + indent_str + "}";
		break;
	default:
		throw std::runtime_error("JSON ndoe error!");
		break;
	}
	return result;
}

myjson::JNode& myjson::JNode::operator=(myjson::JNode&& other) noexcept
{
	this->node_type = other.node_type;
	if (this != &other)
	{
		switch (other.node_type)
		{
		case myjson::JNodeType::JSON_BOOL:
			this->node_value.json_bool = other.node_value.json_bool;
			break;
		case myjson::JNodeType::JSON_INT:
			this->node_value.json_int = other.node_value.json_int;
			break;
		case myjson::JNodeType::JSON_DOUBLE:
			this->node_value.json_double = other.node_value.json_double;
			break;
		case myjson::JNodeType::JSON_STRING:
			this->node_value.json_string = other.node_value.json_string;
			break;
		case myjson::JNodeType::JSON_ARRAY:
			this->node_value.json_array = other.node_value.json_array;
			break;
		case myjson::JNodeType::JSON_OBJECT:
			this->node_value.json_object = other.node_value.json_object;
			break;
		default:
			break;
		}
		other.node_value.json_double = 0.0;
	}
	return *this;
}

myjson::JNode& myjson::JNode::operator=(const myjson::JNode& other)
{
	this->node_type = other.node_type;
	if (this != &other)
	{
		switch (other.node_type)
		{
		case myjson::JNodeType::JSON_BOOL:
			this->node_value.json_bool = other.node_value.json_bool;
			break;
		case myjson::JNodeType::JSON_INT:
			this->node_value.json_int = other.node_value.json_int;
			break;
		case myjson::JNodeType::JSON_DOUBLE:
			this->node_value.json_double = other.node_value.json_double;
			break;
		case myjson::JNodeType::JSON_STRING:
			this->node_value.json_string = new std::string(*other.node_value.json_string);
			break;
		case myjson::JNodeType::JSON_ARRAY:
			this->node_value.json_array = new std::vector<myjson::JNode>(*other.node_value.json_array);
			break;
		case myjson::JNodeType::JSON_OBJECT:
			this->node_value.json_object = new std::unordered_map<std::string, myjson::JNode>(*other.node_value.json_object);
			break;
		default:
			break;
		}
	}
	return *this;
}

myjson::JNode& myjson::JNode::operator=(std::nullptr_t)
{
	if (this->node_type != JNodeType::JSON_NULL)
	{
		this->cleanup();
		this->node_type = JNodeType::JSON_NULL;
	}
	return *this;
}

myjson::JNode& myjson::JNode::operator=(const bool& check)
{
	if (this->node_type != JNodeType::JSON_BOOL)
	{
		this->cleanup();
		this->node_type = JNodeType::JSON_BOOL;
	}
	this->node_value.json_bool = check;
	return *this;
}

myjson::JNode& myjson::JNode::operator=(const std::int64_t& num)
{
	if (this->node_type != JNodeType::JSON_INT)
	{
		this->cleanup();
		this->node_type = JNodeType::JSON_INT;
	}
	this->node_value.json_int = num;
	return *this;
}

myjson::JNode& myjson::JNode::operator=(const double& num)
{
	if (this->node_type != JNodeType::JSON_DOUBLE)
	{
		this->cleanup();
		this->node_type = JNodeType::JSON_DOUBLE;
	}
	this->node_value.json_double = num;
	return *this;
}

myjson::JNode& myjson::JNode::operator=(const std::string& str)
{
	this->cleanup();
	this->node_type = JNodeType::JSON_STRING;
	this->node_value.json_string = new std::string(str);
	return *this;
}

myjson::JNode& myjson::JNode::operator=(const char* str)
{
	return (*this) = std::string(str ? str : "");
}

myjson::JNode& myjson::JNode::operator=(char* str)
{
	return (*this) = static_cast<const char*>(str);
}

myjson::JNode& myjson::JNode::operator=(const std::vector<myjson::JNode>& arr)
{
	this->cleanup();
	this->node_type = JNodeType::JSON_ARRAY;
	this->node_value.json_array = new std::vector<myjson::JNode>(arr);
	return *this;
}

myjson::JNode& myjson::JNode::operator=(const std::unordered_map<std::string, myjson::JNode>& obj)
{
	this->cleanup();
	this->node_type = JNodeType::JSON_OBJECT;
	this->node_value.json_object = new std::unordered_map<std::string, myjson::JNode>(obj);
	return *this;
}

myjson::JNode& myjson::JNode::operator[](const std::string& key)
{
	if (this->node_type != JNodeType::JSON_OBJECT)
	{
		if (this->node_type == JNodeType::JSON_STRING)
		{
			delete this->node_value.json_string;
			this->node_value.json_string = nullptr;
		}
		else if (this->node_type == JNodeType::JSON_ARRAY)
		{
			delete this->node_value.json_array;
			this->node_value.json_array = nullptr;
		}
		this->node_value.json_object = new std::unordered_map<std::string, myjson::JNode>{};
	}
	this->node_type = JNodeType::JSON_OBJECT;
	return (*(this->node_value.json_object))[key];
}

myjson::JNode& myjson::JNode::operator[](const std::uint64_t& index)
{
	if (this->node_type != myjson::JNodeType::JSON_ARRAY)
	{
		this->cleanup();
		this->node_type = myjson::JNodeType::JSON_ARRAY;
	}
	auto cur_value = this->node_value.json_array;
	if (cur_value->size() <= index)
	{
		throw new std::runtime_error("Array out of bounds");
	}
	return (*cur_value)[index];
}
